/*
Copyright 2014 Aaron Batilo

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

//@author: Aaron Batilo

//This class represents a key file and will hold methods related to using one
//This key file has both a string to integer value look up, and an integer value to string lookup for both easy reading and easy storing

#include "Key.h"

//Private member variables
File myRawFile;
map<int, string> myIntToStringKeyTable;
map<string, int> myStringToIntKeyTable;

//Default constructor left empty for empty initializations of private member key variables
Key::Key()
{

}

//This constructor takes in the name of what should be a .KEY extension file
//It will then populate this instance with the proper unencoded information
Key::Key(File inRawFile)
{
	//Set the private member variable for the file handle on the key file on the disk
	myRawFile = inRawFile;

	//Populate this instance's private member map variables
	create();
}

Key::~Key()
{

}

void Key::create()
{
	//This is a list of all of the words from the raw txt file
	vector<string> listOfSplitWords = StringUtility::split(myRawFile.readContents(), ' ');

	//This table will be used to count all repeated words
	//The first parameter of the map is the word itself, the value it holds will be its own count
	map<string, int> wordCountingTable;

	//This table will be the ordered version of the first table
	//Its first parameter is the frequency of each word
	//The second parameter is the list of words that are repeated "first parameter" number of times
	map<int, vector<string>> frequencyTable;

	//Here we create a stream to the key file
	ofstream keyFileOutStream(myRawFile.getNameNoExtension() + ".KEY", ios::binary | ios::out);

	for (int i = 0; i < listOfSplitWords.size(); i++)
	{
		//Represents the currently looked at word
		string currentWord = listOfSplitWords[i];
		wordCountingTable[currentWord]++;
	}

	//Loop through the counted words table and create lists of words that have a specific frequency
	//Re-creating the table this way gives us a way to look at all of the most used words very quickly
	//For example, there might be 3 different words that have all been repeated 10 times
	//We can store all 3 of those words in our list
	for (map<string, int>::iterator wordCountingTableIterator = wordCountingTable.begin(); wordCountingTableIterator != wordCountingTable.end(); ++wordCountingTableIterator)
	{
		//These variables are here for more readability
		string wordToAdd = wordCountingTableIterator->first;
		unsigned int frequency = wordCountingTableIterator->second;

		//Add each word to the vector corresponding to its frequency
		frequencyTable[frequency].push_back(wordToAdd);
	}

	//This will help us only record the 255 most common words
	int wordCount = 0;

	//Ensure the file we want is available
	if (keyFileOutStream.is_open())
	{
		//Reverse through the ordered table to get from greatest to least used words
		for (map<int, vector<string>>::reverse_iterator frequencyTableReverseIterator = frequencyTable.rbegin(); wordCount < 255 && frequencyTableReverseIterator != frequencyTable.rend(); ++frequencyTableReverseIterator)
		{
			//These variables are only defined for readability
			unsigned int frequency = frequencyTableReverseIterator->first;
			vector<string> listOfWords = frequencyTableReverseIterator->second;

			//Creating a lengthOrderedList will re-order the list of words from longest to shorted.
			//This is just a minor efficiency change to potentially compress our raw data more
			LengthOrderedList lengthOrderedList(listOfWords);

			//This is how many words of our current frequency we have
			unsigned int sizeOfListOfWordsWithCurrentFrequency = frequencyTable[frequency].size();

			//Loop through the list of words with this current amount of repetitions
			for (int i = 0; wordCount < 255 && i < sizeOfListOfWordsWithCurrentFrequency; i++)
			{
				//We will give the most repeated word a byte value equivalent to DEC 255 and decrement from there
				unsigned int decByteValue = 255 - wordCount;

				//This variable is mostly for readability
				string currentWord = lengthOrderedList.get(i);

				//Store the final key from int to string
				myIntToStringKeyTable[decByteValue] = currentWord;

				//Store the final key from string to int
				myStringToIntKeyTable[currentWord] = decByteValue;

				//Represent our byte pair decimal value as an ascii character
				unsigned char asciiByteValue = decByteValue;

				//Store the word length as a character to be written to the keyFileOutStream
				unsigned char wordLen = strlen(currentWord.c_str());

				//This string is the format of how we will store our key
				//The first byte represents the byte pair key
				//The second byte represents how many of the following bytes represents the byte pair word
				string nonencodedString = string(1, asciiByteValue) + string(1, wordLen) + currentWord;

				//Convert the non-encoded string to a char* which can now be written to the fileStream
				char* encodedBytes = StringUtility::toCharArray(nonencodedString);
				keyFileOutStream.write(encodedBytes, strlen(encodedBytes));

				//Increment the number of words we've written to the key
				wordCount++;
			}
		}
	}
	//Close the file stream, and write the file to the disk
	keyFileOutStream.close();
}

//This method will populate this instance of a key from a read in key file
void Key::createFromFile(File inKeyFile)
{
	//Ensure the file exists
	if (inKeyFile.isExists())
	{
		//This represents the current line from the file
		string currentLine;

		//Create an input file stream to the file
		ifstream keyFileInStream(inKeyFile.getName().c_str(), ios::binary | ios::in);

		//Ensure the stream is valid
		if (keyFileInStream.is_open())
		{
			//Continue to read through the file until we've reached end of file character
			while (!keyFileInStream.eof())
			{
				//This byte will represent the ascii byte of the byte pair table
				char bytePairByte[1];

				//Read in the single byte
				keyFileInStream.read(bytePairByte, 1);

				//Store the char representation of the byte pair
				unsigned char bytePairByteCharValue = bytePairByte[0];
				//Cast the byte to being an integer for use with the key
				unsigned int bytePairByteDecValue = bytePairByteCharValue;

				//This byte will tell us how many more bytes to read
				char bytePairWordLength[1];

				//Read in the byte. This logic assumes we won't have words that are more than 255 words long
				keyFileInStream.read(bytePairWordLength, 1);

				//This is the casted value to being an int we can send to the key
				unsigned int bytePairWordLengthValue = bytePairWordLength[0];

				//Create a buffer array to hold the word to be read in.
				char wordBuffer[255];
				
				//Place the bytes read in from the file into the buffer
				keyFileInStream.read(wordBuffer, bytePairWordLengthValue);

				//Convert this buffer into a string. This converts the entire buffer to a string
				string bytePairValueUntrimmed(wordBuffer);

				//Trim off the excess bytes
				string bytePairTrimmed(bytePairValueUntrimmed.substr(0, bytePairWordLengthValue));

				//Add this key to the table
				myIntToStringKeyTable[bytePairByteDecValue] = bytePairTrimmed;
			}

			//Once done close the stream to the file
			keyFileInStream.close();
		}
	}
}

//Pass this method the decimal pair to get the word paired with it back
string Key::getIntToStringKeyValue(int inIndex)
{
	return myIntToStringKeyTable[inIndex];
}

//Pass this method a word to get its decimal pair
int Key::getStringToIntKeyValue(string inWord)
{
	return myStringToIntKeyTable[inWord];
}
